﻿#region Using Statements
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using BreakOutBattles.Game;
using BreakOutBattles.Game.Items;
using BreakOutBattles.IO;
using BreakOutBattles.Game.Physics;
using BreakOutBattles.Game.UI;
using BreakOutBattles.Game.Events;
using BreakOutBattles.ParticleSystems;
using System.Threading;
#endregion

namespace BreakOutBattles.Game
{
    /// <summary>
    /// Represents a portion of the screen where the game for one player can be rendered.
    /// </summary>
    public class GameScreen
    {
        #region Delegates and Events
        public delegate void LevelCompletedHandler(object sender, object args);
        public delegate void GameOverHandler(object sender, object args);
        public event LevelCompletedHandler LevelCompleted;
        public event GameOverHandler GameOver;
        #endregion

        #region Fields
        private const float COLLISION_CHECKS_SKIP = 1;
        private const float BACKGROUND_LEFT_OFFSET = 10;
        private const float BACKGROUND_TOP_OFFSET = 100;
        private const int WALL_OFFSET = 20;
        private const int GA_LEFT_OFFSET = 20;
        private const int GA_TOP_OFFSET = 150;

        private Vector2 horizontalBrickSpacing = new Vector2(40, 0);
        private Vector2 verticalBrickSpacing = new Vector2(0, 20);
        private Hud hud;
        private Ball ball;
        private Player player;
        private Vector2 backgroundPos;
        private Rectangle gameArea;
        private Rectangle playableArea;
        private GameLevel gameLevel;
        private IOManager ioManager;
        private Texture2D background;
        private List<GameItem> levelItems;
        private List<ParticleSystem> particleSystems;

        private int collisionCheckSkips = 0;
        private bool gameOver = false;
        private bool levelCompleted = false;
        private bool updateEnabled = true;
        #endregion

        /// <summary>
        /// Creates a new game screen with a specified dimension and a player
        /// that owns the screen.
        /// </summary>
        /// <param name="gameArea">The area of the screen where the contents of the screen can be rendered.</param>
        /// <param name="player">The player that owns this game screen.</param>
        public GameScreen(Rectangle gameArea, Player player)
        {
            this.gameArea = gameArea;
            this.player = player;

            Initialize();
        }

        /// <summary>
        /// Creates an empty game screen which shows a black screen with an icon
        /// indicating that the screen is waiting for a new player to use the screen.
        /// The screen must be set manually afterwards.
        /// </summary>
        public GameScreen()
        {
            ioManager = IOManager.Instance;
        }

        #region Initialization
        /// <summary>
        /// Initializes the components of the game screen.
        /// </summary>
        public void Initialize()
        {
            ioManager = IOManager.Instance;

            hud = new Hud(player, gameArea);
            levelItems = new List<GameItem>();
            particleSystems = new List<ParticleSystem>();

            backgroundPos = new Vector2(gameArea.X + BACKGROUND_LEFT_OFFSET, gameArea.Y + BACKGROUND_TOP_OFFSET);
            playableArea = new Rectangle(gameArea.X + GA_LEFT_OFFSET, gameArea.Y + GA_TOP_OFFSET, gameArea.Width - 20, gameArea.Height - 100);

            RegisterEventHandlers();
        }
        #endregion

        #region Level Creation
        /// <summary>
        /// Creates the three wals that covers the playable area of the game.
        /// </summary>
        private void CreateWalls()
        {
            Wall topWall = new Wall(new Rectangle(playableArea.X, playableArea.Y - WALL_OFFSET, playableArea.Width, WALL_OFFSET));
            levelItems.Add(topWall);

            Wall leftWall = new Wall(new Rectangle(playableArea.X - WALL_OFFSET, playableArea.Y, WALL_OFFSET, playableArea.Height));
            levelItems.Add(leftWall);

            Wall rightWall = new Wall(new Rectangle(playableArea.Right - WALL_OFFSET, playableArea.Y, WALL_OFFSET, playableArea.Height));
            levelItems.Add(rightWall);
        }

        /// <summary>
        /// Creates a player controlled paddle to be used in the game.
        /// </summary>
        private void CreatePaddle()
        {
            Paddle paddle = new Paddle(player, playableArea, ioManager.LoadTextureFromId("pb"));
            levelItems.Add(paddle);
        }

        /// <summary>
        /// Creates the ball.
        /// </summary>
        private void CreateBall()
        {
            ball = new Ball(playableArea, ioManager.LoadTextureFromId("bg"));
            ball.OutOfBoundsEvent += player.BallOutOfBoundsHandler;
            levelItems.Add(ball);
        }

        /// <summary>
        /// Creates the background to be used in the game.
        /// </summary>
        private void CreateBackground()
        {
            background = ioManager.LoadTexture(ContentFolders.BACKGROUNDS, gameLevel.LevelBackground);
        }

        /// <summary>
        /// Creates the bricks that composes the level.
        /// </summary>
        private void CreateBricks()
        {
            Vector2 currentBrickPosition = new Vector2(playableArea.Left, playableArea.Top);

            foreach (string levelDataLine in gameLevel.BrickData)
            {
                foreach (char levelItemID in levelDataLine.ToCharArray())
                {
                    Brick brick = GameItemFactory.CreateGameItem(this, levelItemID.ToString(), currentBrickPosition) as Brick;

                    if (brick != null)
                    {
                        brick.BrickDestroyedEvent += player.BrickDestroyedHandler;
                        brick.BrickDestroyedEvent += this.LevelBrickDestroyed;
                        levelItems.Add(brick);
                    }

                    currentBrickPosition += horizontalBrickSpacing;
                }

                currentBrickPosition.X = playableArea.Left;
                currentBrickPosition += verticalBrickSpacing;
            }
        }

        /// <summary>
        /// Loads level data from an external resource to create the 
        /// components that are going to be used in the game.
        /// </summary>
        /// <param name="mapName">The name of the map to load</param>
        public void LoadLevel(string levelName)
        {
            gameLevel = ioManager.LoadLevel(levelName);

            CreateBackground();
            CreateBricks();
            CreateWalls();
            CreatePaddle();
            CreateBall();
        }
        #endregion

        #region Event Handling
        /// <summary>
        /// Register the necesary event handlers to handle different actions through the game.
        /// </summary>
        private void RegisterEventHandlers()
        {
            player.OutOfLifes += PlayerOutOfLifesHandler;
        }

        /// <summary>
        /// Unregister all event handlers handled by this game screen.
        /// </summary>
        private void UnregisterEventHandlers()
        {
            player.OutOfLifes -= PlayerOutOfLifesHandler;
        }

        /// <summary>
        /// Fires an event indicating that the player of this gamescreen not longer is able to play.
        /// </summary>
        private void FireGameOverEvent()
        {
            if (GameOver != null)
            {
                GameOver(this, player);
            }
        }

        /// <summary>
        /// Fires an event indicating that the player completed the level.
        /// </summary>
        private void FireLevelCompletedEvent()
        {
            if (LevelCompleted != null)
            {
                LevelCompleted(this, player);
            }
        }

        /// <summary>
        /// Handles the PlayerOutofLifes event.
        /// </summary>
        public void PlayerOutOfLifesHandler(object sender, object args)
        {
            FireGameOverEvent();
        }

        /// <summary>
        /// Handles the LevelBrickDestroyed event.
        /// </summary>
        public void LevelBrickDestroyed(object sender, BrickDestroyedArgs args)
        {
            gameLevel.DestroyableItemsCount--;
        }
        #endregion

        #region Particle Handling
        /// <summary>
        /// Creates a new particle system to be handled by this game screen.
        /// </summary>
        /// <param name="particleSystemType">The type of particle system to be created.</param>
        public void CreateParticleSystem(Vector2 particleOrigin, ParticleSystemTypes particleSystemType)
        {
            switch (particleSystemType)
            {
                case ParticleSystemTypes.SMOKE_PLUME:
                    particleSystems.Add(new SmokePlumeParticleSystem(particleOrigin, 9));
                    break;

                default:
                    particleSystems.Add(new SmokePlumeParticleSystem(particleOrigin, 9));
                    break;
            }
        }

        /// <summary>
        /// Adds a new particle system to be handled by this game screen.
        /// </summary>
        /// <param name="particleSystem">The particle system to add.</param>
        public void AddParticleSystem(ParticleSystem particleSystem)
        {
            particleSystem.RenderArea = playableArea;
            particleSystems.Add(particleSystem);
        }
        #endregion

        /// <summary>
        /// Resets the game screen as it was just before the game started.
        /// </summary>
        public void Reset()
        {
            gameOver = false;
            levelCompleted = false;
            updateEnabled = true;
            gameLevel.Reset();

            foreach (GameItem levelItem in levelItems)
            {
                levelItem.Reset();
            }
        }

        /// <summary>
        /// Prepares the game screen before a new round starts.
        /// </summary>
        public void PrepareNextRound()
        {
            player.Reset(ResetPlayerOptions.ALL_NOT_SCORE);
            hud.Reset();
            Reset();
        }

        /// <summary>
        /// Changes game screen states and notifies other components handled by this
        /// game screen that a game round is over.
        /// </summary>
        public void HandleRoundOver()
        {
            hud.ShowScoreTable = true;
            levelCompleted = true;
        }

        /// <summary>
        /// Clears current level data, unregisters event handlers and other operations handled by this game screen.
        /// </summary>
        public void Terminate()
        {
            particleSystems.Clear();
            levelItems.Clear();
            UnregisterEventHandlers();
        }

        #region Properties
        /// <summary>
        /// Gets and sets a flag indicating if the game screen is able to update its
        /// components. Setting a false value will also pause the game on this gamescreen.
        /// </summary>
        public bool UpdateEnabled
        {
            get { return updateEnabled; }
            set { updateEnabled = value; }
        }

        /// <summary>
        /// Gets the player that owns this game screen.
        /// </summary>
        public Player Player
        {
            get { return player; }
            set { player = value; }
        }

        /// <summary>
        /// Gets and sets the area that this game screen can use.
        /// </summary>
        public Rectangle GameArea
        {
            get { return gameArea; }
            set { gameArea = value; }
        }
        #endregion

        #region Update
        private void UpdateLevel(GameTime gameTime)
        {
            if (updateEnabled && !gameOver && !levelCompleted)
            {
                foreach (GameItem levelItem in levelItems)
                {
                    if (levelItem.Active)
                    {
                        levelItem.Update(gameTime);

                        if (levelItem != ball)
                        {
                            ball.CollisionCheck(levelItem as ISolidObject);
                            //collisionCheckSkips = 0;
                        }
                    }
                }
                //collisionCheckSkips++;

                foreach (ParticleSystem particleSystem in particleSystems)
                {
                    ThreadPool.QueueUserWorkItem(particleSystem.Update, gameTime);
                }
            }

            if (gameLevel.DestroyableItemsCount <= 0)
            {
                FireLevelCompletedEvent();
            }
        }

        /// <summary>
        /// Updates the game screen and runs collision tests.
        /// </summary>
        public void Update(GameTime gameTime)
        {
            if (GameState.CurrentState == GameStates.GAME_RUNNING)
            {
                UpdateLevel(gameTime);
            }

            if (!player.IsRemotePlayer && (GameState.CurrentState == GameStates.PLAYER_JOIN_WAIT))
            {
                UpdateLevel(gameTime);
            }

            if (player.IsRemotePlayer && (GameState.CurrentState == GameStates.PLAYER_JOIN_WAIT))
            {
            }
        }
        #endregion

        #region Draw
        private void DrawLevel(SpriteBatch spriteBatch)
        {
            spriteBatch.Draw(background, backgroundPos, Color.White);

            if (!gameOver && !levelCompleted)
            {
                foreach (GameItem levelItem in levelItems)
                {
                    levelItem.Draw(spriteBatch);
                }

                foreach (ParticleSystem particleSystem in particleSystems)
                {
                    particleSystem.Draw(spriteBatch);
                }
            }

            hud.Draw(spriteBatch);
        }

        private void DrawJoinWait(SpriteBatch spriteBatch)
        {

        }

        /// <summary>
        /// Draw the level components on the game screen.
        /// </summary>
        public void Draw(SpriteBatch spriteBatch)
        {
            if (GameState.CurrentState == GameStates.GAME_RUNNING)
            {
                DrawLevel(spriteBatch);
            }

            if (!player.IsRemotePlayer && (GameState.CurrentState == GameStates.PLAYER_JOIN_WAIT))
            {
                DrawLevel(spriteBatch);
            }

            if (player.IsRemotePlayer && (GameState.CurrentState == GameStates.PLAYER_JOIN_WAIT))
            {
                DrawJoinWait(spriteBatch);
            }
        }
        #endregion
    }
}
